/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University. All rights reserved.
 * 
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license; 2) All redistributions in binary form
 * must reproduce the above copyright notice, this list of conditions and the
 * disclaimer listed in this license in the documentation and/or other materials
 * provided with the distribution; 3) Any documentation included with all
 * redistributions must include the following acknowledgement:
 * 
 * "This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 * 
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 * 
 * 4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University. For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202. 5) Products
 * derived from this software may not be called NaradaBrokering, nor may Indiana
 * University or Community Grids Lab or NaradaBrokering appear in their name,
 * without prior written permission of ARTI.
 * 
 * 
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of any
 * other entity. Indiana University disclaims any liability to any recipient for
 * claims brought by any other entity based on infringement of intellectual
 * property rights or otherwise.
 * 
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 * NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 * GENERATED USING SOFTWARE.
 */
package cgl.narada.matching;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import cgl.narada.event.NBEvent;
import cgl.narada.event.TemplateProfileAndSynopsisTypes;
import cgl.narada.matching.integer.ClientIntegerMatching;
import cgl.narada.matching.integer.IntegerMatching;
import cgl.narada.matching.regex.ClientRegExMatching;
import cgl.narada.matching.regex.RegExMatching;
import cgl.narada.matching.string.ClientStringMatching;
import cgl.narada.matching.string.StringMatching;
import cgl.narada.matching.tagvaluepairs.TagValueClientMatching;
import cgl.narada.matching.tagvaluepairs.TagValueMatching;
import cgl.narada.matching.xmlxpath.ClientXPathMatching;
import cgl.narada.matching.xmlxpath.XPathMatching;
import cgl.narada.protocol.Destinations;
import cgl.narada.protocol.ProfilePropagationProtocol;
import cgl.narada.protocol.Protocol;

/**
 * Manages all the matching trees hosted within the system
 * 
 * @author Shrideep Pallickara $Revision$ $Date$
 */

public class ProfileMatchingTrees {
   private Vector stringMatchingVector;

   private Vector integerMatchingVector;

   private Vector xpathMatchingVector;

   private Vector regexMatchingVector;

   private Vector tagvaluepairsMatchingVector;

   private int systemLevel = 3;

   private Destinations thisDestination;

   private ProfilePropagationProtocol ppProtocol;

   /**
    * This vector is used to mainatain information about profiles at different
    * levels. This is used during node joins to serialize and transfer
    * appropriate information to the node in question.
    */
   private Vector profilesAtLevels;

   private boolean debug = false;
   private String moduleName = "ProfileMatchingTrees: ";

   public ProfileMatchingTrees(int systemLevel, Destinations thisDestination,
         ProfilePropagationProtocol ppProtocol) {
      this.systemLevel = systemLevel;
      this.thisDestination = thisDestination;
      this.ppProtocol = ppProtocol;

      int numOfEntries = systemLevel + 2; // Entries for -1, 0, 1, 2, ..
                                          // systemLevel

      profilesAtLevels = new Vector();
      for (int i = 0; i < numOfEntries; i++) {
         profilesAtLevels.addElement(new Hashtable());
      }

      stringMatchingVector = new Vector(numOfEntries);

      ClientStringMatching _clientStringMatching = new ClientStringMatching(
                                                                            0,
                                                                            systemLevel,
                                                                            ppProtocol);
      _clientStringMatching.setNodeDestination(thisDestination);
      stringMatchingVector.addElement(_clientStringMatching);

      for (int i = 1; i < numOfEntries; i++) {
         stringMatchingVector.addElement(new Object());
      }

      integerMatchingVector = new Vector(numOfEntries);
      ClientIntegerMatching _clientIntegerMatching = new ClientIntegerMatching(
                                                                               0,
                                                                               systemLevel,
                                                                               ppProtocol);
      _clientIntegerMatching.setNodeDestination(thisDestination);
      integerMatchingVector.addElement(_clientIntegerMatching);

      for (int i = 1; i < numOfEntries; i++) {
         integerMatchingVector.addElement(new Object());
      }

      xpathMatchingVector = new Vector(numOfEntries);
      ClientXPathMatching _clientXPathMatching = new ClientXPathMatching(
                                                                         0,
                                                                         systemLevel,
                                                                         ppProtocol);
      _clientXPathMatching.setNodeDestination(thisDestination);
      xpathMatchingVector.addElement(_clientXPathMatching);

      for (int i = 1; i < numOfEntries; i++) {
         xpathMatchingVector.addElement(new Object());
      }

      regexMatchingVector = new Vector(numOfEntries);
      ClientRegExMatching _clientRegExMatching = new ClientRegExMatching(
                                                                         0,
                                                                         systemLevel,
                                                                         ppProtocol);
      _clientRegExMatching.setNodeDestination(thisDestination);
      regexMatchingVector.addElement(_clientRegExMatching);

      for (int i = 1; i < numOfEntries; i++) {
         regexMatchingVector.addElement(new Object());
      }

      tagvaluepairsMatchingVector = new Vector(numOfEntries);
      TagValueClientMatching _tagValueClientMatching = new TagValueClientMatching(
                                                                                  0,
                                                                                  systemLevel,
                                                                                  ppProtocol);
      _tagValueClientMatching.setNodeDestination(thisDestination);
      tagvaluepairsMatchingVector.addElement(_tagValueClientMatching);

      for (int i = 1; i < numOfEntries; i++) {
         tagvaluepairsMatchingVector.addElement(new Object());
      }

   }

   public void initializeMatchingTreesAtLevel(int level) {
      int index = level + 1; // ::
      StringMatching _stringMatching = new StringMatching(level, systemLevel,
                                                          ppProtocol);
      _stringMatching.setNodeDestination(thisDestination);
      stringMatchingVector.setElementAt(_stringMatching, index);

      IntegerMatching _integerMatching = new IntegerMatching(level,
                                                             systemLevel,
                                                             ppProtocol);
      _integerMatching.setNodeDestination(thisDestination);
      integerMatchingVector.setElementAt(_integerMatching, index);

      XPathMatching _xpathMatching = new XPathMatching(level, systemLevel,
                                                       ppProtocol);
      _xpathMatching.setNodeDestination(thisDestination);
      xpathMatchingVector.setElementAt(_xpathMatching, index);

      RegExMatching _regexMatching = new RegExMatching(level, systemLevel,
                                                       ppProtocol);
      _regexMatching.setNodeDestination(thisDestination);
      regexMatchingVector.setElementAt(_regexMatching, index);

      TagValueMatching _tagvalueMatching = new TagValueMatching(level,
                                                                systemLevel,
                                                                ppProtocol);
      _tagvalueMatching.setNodeDestination(thisDestination);
      tagvaluepairsMatchingVector.setElementAt(_tagvalueMatching, index);
   }

   public void manageSubscriptionProfile(Profile profile, boolean addProfile,
                                         int level) {
      int profileType = profile.getProfileType();
      if (debug) {
         System.out.println(moduleName + profile + ", level=[" + level + "]");
      }
      
      level++; // ::

      MatchingTree _matchingTree = null;

      if (profileType == TemplateProfileAndSynopsisTypes.STRING) {
         _matchingTree = (MatchingTree) stringMatchingVector.elementAt(level);
      }

      if (profileType == TemplateProfileAndSynopsisTypes.INTEGER) {
         _matchingTree = (MatchingTree) integerMatchingVector.elementAt(level);
      }

      if (profileType == TemplateProfileAndSynopsisTypes.XML) {
         _matchingTree = (MatchingTree) xpathMatchingVector.elementAt(level);
      }

      if (profileType == TemplateProfileAndSynopsisTypes.REGULAR_EXPRESSIONS) {
         _matchingTree = (MatchingTree) regexMatchingVector.elementAt(level);
      }

      if (profileType == TemplateProfileAndSynopsisTypes.TAG_VALUE_PAIRS) {
         _matchingTree = (MatchingTree) tagvaluepairsMatchingVector
                                                                   .elementAt(level);
      }

      if (_matchingTree == null) {
         System.out.println(moduleName + "Matching tree to manage profile "
                            + profile + " has not been initialized");
         return;
      }

      if (addProfile) {
         _matchingTree.addSubscriptionProfile(profile);
      } else {
         _matchingTree.removeSubscriptionProfile(profile);
      } /* end addProfile */

      
      Hashtable profileListForLevel = 
         (Hashtable) profilesAtLevels.elementAt(level);
      Object key = profile.getProfileId();

      if (addProfile) {
         if (!profileListForLevel.containsKey(key)) {
            profileListForLevel.put(key, profile);
         }
      } else {
         if (profileListForLevel.containsKey(key)) {
            profileListForLevel.remove(key);
         }
      }

      // ::
      /**
       * if (level == 0) { _matchingTree.propagateChangesToHigherLevels(profile,
       * addProfile); }
       */

   }

   public byte[] marshallProfilesForSpecifiedLevelOnwards(int level) {
      ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream();
      DataOutputStream dout = new DataOutputStream(baOutputStream);
      byte[] marshalledBytes = null;

      try {
         int numOfLevelsMarshalled = systemLevel + 1 - level;
         
         dout.write(Protocol.PROFILES_EXCHANGE);
         
         dout.writeInt(numOfLevelsMarshalled);
         for (int i = level; i <= systemLevel; i++) {
            marshallProfilesOverLevel(i, dout);
         }
         
         dout.flush();
         marshalledBytes = baOutputStream.toByteArray();
         baOutputStream = null;
         dout = null;
         
      } catch (IOException ioe) {
         System.out.println(moduleName + "Error marshalling profiles ");
      }
      return marshalledBytes;
   }

   private void marshallProfilesOverLevel(int level, DataOutputStream dout)
         throws IOException {
      int index = level + 1;
      
      dout.writeInt(level);
      
      Hashtable profileListForLevel = 
         (Hashtable) profilesAtLevels.elementAt(index);
      
      int numOfEntries = profileListForLevel.size();
      Enumeration e = profileListForLevel.elements();
      dout.writeInt(numOfEntries);
      
      if (debug) {
         System.out.println(moduleName + "Number of entries =" + numOfEntries +
               ", e.hasMoreElements()=" + e.hasMoreElements());
      }
      
      while (e.hasMoreElements()) {
         Profile _profile = (Profile)e.nextElement();
         if (debug) {
            System.out.println(moduleName + _profile);
         }
         byte[] profileBytes = _profile.getBytes();
         int profileType = _profile.getProfileType();
         dout.writeInt(profileType);
         dout.writeInt(profileBytes.length);
         dout.write(profileBytes);
      }
   }
   
   
   
   public void processMarshalledProfiles(byte[] marshalledBytes) {
      ByteArrayInputStream baInputStream = new ByteArrayInputStream(marshalledBytes);
      DataInputStream din = new DataInputStream(baInputStream);
      
      try {
         din.readByte(); // Corresponding to the PROFILE_EXCHANGE header
         int numOfLevelsMarshalled = din.readInt();
         
         if (debug) {
            System.out.println(moduleName + "Number of levels marshalled = " +
                  numOfLevelsMarshalled);
         }
         
         for (int i=0; i< numOfLevelsMarshalled; i++) {
            int level = din.readInt();
            unmarshallProfilesForLevel(level, din);
         }
         
      } catch (IOException ioe) {
         System.out.println(moduleName + "Problems unmarshalling ProfileBytes");
      }
   }
   

   private void unmarshallProfilesForLevel(int level, DataInputStream din) 
      throws IOException {

      int numOfEntries = din.readInt();
      
      for (int i=0; i< numOfEntries; i++) {
         int profileType = din.readInt();
         byte[] profileBytes = new byte[din.readInt()];
         din.readFully(profileBytes);
         
         Profile _profile = ProfileReconstructor.getProfile(profileType, profileBytes);
         manageSubscriptionProfile(_profile, true, level);
      }
      
   }
   
   /**
    * This method computes the destinations associated with the matching of an
    * event at a particular level
    * 
    * @param nbEvent
    *           The event that needs to be matched.
    * @param level
    *           The destinations at level (<i>level-1</i>) that we are
    *           interested in.
    * @return The destinations that are associated with the event.
    */
   public Object computeEventDestinations(NBEvent nbEvent, int level) {
      int eventType = nbEvent.getContentSynopsisType();
      MatchingTree _matchingTree = null;

      if (eventType == TemplateProfileAndSynopsisTypes.STRING) {
         _matchingTree = (MatchingTree) stringMatchingVector
                                                            .elementAt(level + 1);
      }

      if (eventType == TemplateProfileAndSynopsisTypes.INTEGER) {
         _matchingTree = (MatchingTree) integerMatchingVector
                                                             .elementAt(level + 1);
      }

      if (eventType == TemplateProfileAndSynopsisTypes.XML) {
         _matchingTree = (MatchingTree) xpathMatchingVector
                                                           .elementAt(level + 1);
      }

      if (eventType == TemplateProfileAndSynopsisTypes.REGULAR_EXPRESSIONS) {
         _matchingTree = (MatchingTree) regexMatchingVector
                                                           .elementAt(level + 1);
      }

      if (eventType == TemplateProfileAndSynopsisTypes.TAG_VALUE_PAIRS) {
         _matchingTree = (MatchingTree) tagvaluepairsMatchingVector
                                                                   .elementAt(level + 1);
      }

      if (_matchingTree == null) {
         System.out.println(moduleName + "Unable to match event with content "
                            + "synopsis of type [" + eventType + "]");
         return null;
      }

      return _matchingTree.matchEvent(nbEvent);
   }

   public void connectionToClientLost(Object clientIdentifier, String linkId) {

      ClientStringMatching _clientStringMatching = (ClientStringMatching) stringMatchingVector
                                                                                              .elementAt(0);
      _clientStringMatching.removeProfilesForDestination(clientIdentifier,
            linkId);

      ClientIntegerMatching _clientIntegerMatching = (ClientIntegerMatching) integerMatchingVector
                                                                                                  .elementAt(0);
      _clientIntegerMatching.removeProfilesForDestination(clientIdentifier,
            linkId);

      ClientXPathMatching _clientXPathMatching = (ClientXPathMatching) xpathMatchingVector
                                                                                          .elementAt(0);
      _clientXPathMatching.removeProfilesForDestination(clientIdentifier,
            linkId);

      ClientRegExMatching _clientRegExMatching = (ClientRegExMatching) regexMatchingVector
                                                                                          .elementAt(0);
      _clientRegExMatching.removeProfilesForDestination(clientIdentifier,
            linkId);

      TagValueClientMatching _tagValueClientMatching = (TagValueClientMatching) tagvaluepairsMatchingVector
                                                                                                           .elementAt(0);
      _tagValueClientMatching.removeProfilesForDestination(clientIdentifier,
            linkId);
   }

}
